/*
 * Copyright 2011 Peter Kuterna
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.sgroup.imenu.controlview;

import org.sgroup.imenu.R;
import org.sgroup.imenu.controladapter.ITabsAdapter;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.text.TextUtils.TruncateAt;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class ITabs extends ViewGroup implements OnPageChangeListener {

	protected final String TAG = "SwipeyTabs";

	private ITabsAdapter mAdapter;

	private int mCurrentPos = -1;

	// height of the bar at the bottom of the tabs
	private int mBottomBarHeight = 2;
	// height of the indicator for the fronted tab
	private int mTabIndicatorHeight = 3;
	// color for the bottom bar, fronted tab
	private int mBottomBarColor = 0xff96aa39;
	// text color for all other tabs
	private int mTextColor = 0xff949494;

	// holds the positions of the fronted tabs
	private int[] mFrontedTabPos;
	// holds the positions of the target position when swiping left
	private int[] mLeftTabPos;
	// holds the positions of the target position when swiping right
	private int[] mRightTabPos;
	// holds the positions of the current position on screen
	private int[] mCurrentTabPos;

	private Paint mCachedTabPaint;

	private int mWidth = -1;

	public ITabs(Context context) {
		this(context, null);
	}

	public ITabs(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}

	public ITabs(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);

		final TypedArray a = context.obtainStyledAttributes(attrs,
				R.styleable.ITabs, defStyle, 0);

		mBottomBarColor = a.getColor(R.styleable.ITabs_bottomBarColor,
				mBottomBarColor);
		mBottomBarHeight = a.getDimensionPixelSize(
				R.styleable.ITabs_bottomBarHeight, 2);
		mTabIndicatorHeight = a.getDimensionPixelSize(
				R.styleable.ITabs_tabIndicatorHeight, 3);

		a.recycle();

		init();
	}

	/**
	 * Initialize the SwipeyTabs {@link ViewGroup}
	 */
	private void init() {
		// enable the horizontal fading edges which will be drawn by the parent
		// View
		setHorizontalFadingEdgeEnabled(true);
		setFadingEdgeLength((int) (getResources().getDisplayMetrics().density * 35.0f + 0.5f));
		setWillNotDraw(false);

		mCachedTabPaint = new Paint();
		mCachedTabPaint.setColor(mBottomBarColor);
	}

	/**
	 * Set the adapter.
	 * 
	 * @param adapter
	 */
	public void setAdapter(ITabsAdapter adapter) {
		if (mAdapter != null) {
			// TODO: data set observer
		}

		mAdapter = adapter;
		mCurrentPos = -1;
		mFrontedTabPos = null;
		mLeftTabPos = null;
		mRightTabPos = null;
		mCurrentTabPos = null;

		// clean up our childs
		removeAllViews();

		if (mAdapter != null) {
			final int count = mAdapter.getCount();

			// add the child text views
			for (int i = 0; i < count; i++) {
				addView(mAdapter.getTab(i, this));
			}

			mCurrentPos = 0;
			mFrontedTabPos = new int[count];
			mLeftTabPos = new int[count];
			mRightTabPos = new int[count];
			mCurrentTabPos = new int[count];

			mWidth = -1;

			requestLayout();
		}
	}

	/**
	 * Calculate the fronted, left and right positions
	 * 
	 * @param forceLayout
	 *            force the current positions to the values of the calculated
	 *            fronted positions
	 */
	private void updateTabPositions(boolean forceLayout) {
		if (mAdapter == null) {
			return;
		}

		calculateTabPosition(mCurrentPos, mFrontedTabPos);
		calculateTabPosition(mCurrentPos + 1, mLeftTabPos);
		calculateTabPosition(mCurrentPos - 1, mRightTabPos);

		updateEllipsize();

		if (forceLayout) {
			final int count = mAdapter.getCount();
			for (int i = 0; i < count; i++) {
				mCurrentTabPos[i] = mFrontedTabPos[i];
			}
		}
	}

	/**
	 * Calculate the position of the tabs.
	 * 
	 * @param position
	 *            the position of the fronted tab
	 * @param tabPositions
	 *            the array in which to store the result
	 */
	private void calculateTabPosition(int position, int[] tabPositions) {
		if (mAdapter == null) {
			return;
		}

		final int count = mAdapter.getCount();

		if (position >= 0 && position < count) {
			final int width = getMeasuredWidth();

			final View centerTab = getChildAt(position);
			tabPositions[position] = width / 2 - centerTab.getMeasuredWidth()
					/ 2;
			for (int i = position - 1; i >= 0; i--) {
				final TextView tab = (TextView) getChildAt(i);
				if (i == position - 1) {
					tabPositions[i] = 0 - tab.getPaddingLeft();
				} else {
					tabPositions[i] = 0 - tab.getMeasuredWidth() - width;
				}
				tabPositions[i] = Math.min(tabPositions[i], tabPositions[i + 1]
						- tab.getMeasuredWidth());
			}
			for (int i = position + 1; i < count; i++) {
				final TextView tab = (TextView) getChildAt(i);
				if (i == position + 1) {
					tabPositions[i] = width - tab.getMeasuredWidth()
							+ tab.getPaddingRight();
				} else {
					tabPositions[i] = width * 2;
				}
				final TextView prevTab = (TextView) getChildAt(i - 1);
				tabPositions[i] = Math.max(tabPositions[i], tabPositions[i - 1]
						+ prevTab.getMeasuredWidth());
			}
		} else {
			for (int i = 0; i < tabPositions.length; i++) {
				tabPositions[i] = -1;
			}
		}
	}

	/**
	 * Update the ellipsize of the text views
	 */
	private void updateEllipsize() {
		if (mAdapter == null) {
			return;
		}

		final int count = mAdapter.getCount();

		for (int i = 0; i < count; i++) {
			TextView tab = (TextView) getChildAt(i);

			if (i < mCurrentPos) {
				tab.setEllipsize(null);
				tab.setGravity(Gravity.RIGHT | Gravity.CENTER_VERTICAL);
			} else if (i == mCurrentPos) {
				tab.setEllipsize(TruncateAt.END);
				tab.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
			} else if (i > mCurrentPos) {
				tab.setEllipsize(null);
				tab.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
			}
		}
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		int widthSize = MeasureSpec.getSize(widthMeasureSpec);

		measureTabs(widthMeasureSpec, heightMeasureSpec);

		int height = 0;
		final View v = getChildAt(0);
		if (v != null) {
			height = v.getMeasuredHeight();
		}

		setMeasuredDimension(
				resolveSize(getPaddingLeft() + widthSize + getPaddingRight(),
						widthMeasureSpec),
				resolveSize(height + mBottomBarHeight + getPaddingTop()
						+ getPaddingBottom(), heightMeasureSpec));

		if (mWidth != widthSize) {
			mWidth = widthSize;
			updateTabPositions(true);
		}
	}

	/**
	 * Measure our tab text views
	 * 
	 * @param widthMeasureSpec
	 * @param heightMeasureSpec
	 */
	private void measureTabs(int widthMeasureSpec, int heightMeasureSpec) {
		if (mAdapter == null) {
			return;
		}

		int widthSize = MeasureSpec.getSize(widthMeasureSpec);
		final int maxWidth = (int) (widthSize * 0.6);

		final int count = mAdapter.getCount();

		for (int i = 0; i < count; i++) {
			LayoutParams layoutParams = (LayoutParams) getChildAt(i)
					.getLayoutParams();
			final int widthSpec = MeasureSpec.makeMeasureSpec(maxWidth,
					MeasureSpec.AT_MOST);
			final int heightSpec = MeasureSpec.makeMeasureSpec(
					layoutParams.height, MeasureSpec.EXACTLY);
			getChildAt(i).measure(widthSpec, heightSpec);
		}
	}

	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		if (mAdapter == null) {
			return;
		}

		final int count = mAdapter.getCount();

		for (int i = 0; i < count; i++) {
			View v = getChildAt(i);

			v.layout(mCurrentTabPos[i], this.getPaddingTop(), mCurrentTabPos[i]
					+ v.getMeasuredWidth(),
					this.getPaddingTop() + v.getMeasuredHeight());
		}
	}

	@Override
	protected void dispatchDraw(Canvas canvas) {
		if (mCurrentPos != -1) {
			// calculate the relative position of the fronted tab to set the
			// alpha channel of the tab indicator
			final int tabSelectedTop = getHeight() - getPaddingBottom()
					- mBottomBarHeight - mTabIndicatorHeight;
			final View currentTab = getChildAt(mCurrentPos);
			final int centerOfTab = (mCurrentTabPos[mCurrentPos] + currentTab
					.getMeasuredWidth()) - (currentTab.getMeasuredWidth() / 2);
			final int center = getWidth() / 2;
			final int centerDiv3 = center / 3;
			final float relativePos = 1.0f - Math.min(
					Math.abs((float) (centerOfTab - center)
							/ (float) (centerDiv3)), 1.0f);
			
			mCachedTabPaint.setAlpha((int) (255 * relativePos));
			canvas.drawRect(
					mCurrentTabPos[mCurrentPos],
					tabSelectedTop,
					mCurrentTabPos[mCurrentPos] + currentTab.getMeasuredWidth(),
					tabSelectedTop + mTabIndicatorHeight, mCachedTabPaint);

			// set the correct text colors on the text views
			final int count = mAdapter.getCount();
			for (int i = 0; i < count; i++) {
				final TextView tab = (TextView) getChildAt(i);
				if (mCurrentPos == i) {
					tab.setTextColor(interpolateColor(mBottomBarColor,
							mTextColor, 1.0f - relativePos));
				} else {
					tab.setTextColor(mTextColor);
				}
			}

		}

		super.dispatchDraw(canvas);
	}

	@Override
	public void draw(Canvas canvas) {
		super.draw(canvas);

		// draw the bottom bar
		final int bottomBarTop = getHeight() - getPaddingBottom()
				- mBottomBarHeight;
		mCachedTabPaint.setAlpha(0xff);
		canvas.drawRect(0, bottomBarTop, getWidth(), bottomBarTop
				+ mBottomBarHeight, mCachedTabPaint);
	}

	@Override
	protected float getLeftFadingEdgeStrength() {
		// forced so that we will always have the left fading edge
		return 1.0f;
	}

	@Override
	protected float getRightFadingEdgeStrength() {
		// forced so that we will always have the right fading edge
		return 1.0f;
	}

	public void onPageScrollStateChanged(int state) {
	}

	public void onPageScrolled(int position, float positionOffset,
			int positionOffsetPixels) {
		if (mAdapter == null) {
			return;
		}

		final int count = mAdapter.getCount();

		float x = 0.0f;
		int dir = 0;

		// detect the swipe direction
		if (positionOffsetPixels != 0 && mCurrentPos == position) {
			dir = -1;
			x = positionOffset;
		} else if (positionOffsetPixels != 0 && mCurrentPos != position) {
			dir = 1;
			x = 1.0f - positionOffset;
		}
		
		// update the current positions
		for (int i = 0; i < count; i++) {
			final float curX = mFrontedTabPos[i];
			float toX = 0.0f;

			if (dir < 0) {
				toX = mLeftTabPos[i];
			} else if (dir > 0) {
				toX = mRightTabPos[i];
			} else {
				toX = mFrontedTabPos[i];
			}

			final int offsetX = (int) ((toX - curX) * x + 0.5f);
			final int newX = (int) (curX + offsetX);

			mCurrentTabPos[i] = newX;
		}

		requestLayout();
		invalidate();
	}

	public void onPageSelected(int position) {
		mCurrentPos = position;
		updateTabPositions(false);
	}

	private int interpolateColor(final int color1, final int color2,
			float fraction) {
		final float alpha1 = Color.alpha(color1) / 255.0f;
		final float red1 = Color.red(color1) / 255.0f;
		final float green1 = Color.green(color1) / 255.0f;
		final float blue1 = Color.blue(color1) / 255.0f;

		final float alpha2 = Color.alpha(color2) / 255.0f;
		final float red2 = Color.red(color2) / 255.0f;
		final float green2 = Color.green(color2) / 255.0f;
		final float blue2 = Color.blue(color2) / 255.0f;

		final float deltaAlpha = alpha2 - alpha1;
		final float deltaRed = red2 - red1;
		final float deltaGreen = green2 - green1;
		final float deltaBlue = blue2 - blue1;

		float alpha = alpha1 + (deltaAlpha * fraction);
		float red = red1 + (deltaRed * fraction);
		float green = green1 + (deltaGreen * fraction);
		float blue = blue1 + (deltaBlue * fraction);

		alpha = Math.max(Math.min(alpha, 1f), 0f);
		red = Math.max(Math.min(red, 1f), 0f);
		green = Math.max(Math.min(green, 1f), 0f);
		blue = Math.max(Math.min(blue, 1f), 0f);

		return Color.argb((int) (alpha * 255.0f), (int) (red * 255.0f),
				(int) (green * 255.0f), (int) (blue * 255.0f));
	}

}
